﻿//=======================================================================================
//
//  Purpose: Main program for controlling a 8 x 8 x 8 LED cube using 74HC595 shift
//           registers and 74HC238 3-to-8 line multiplers using the Netduino Mini
//           microcontroller.
//
//  Copyright (C) 2011 Mark Stevens
//
//  This software is destributed under the MS-PL licence agreement a copy of which can
//  be found on the codeplex page http://netduinoledcontrol.codeplex.com and in the
//  Licence.txt file distributed with this project.
//
//=======================================================================================
using System;
using System.Threading;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT;
using System.Collections;

namespace Coding4Fun.NetduinoLEDControl
{
    /// <summary>
    /// Main program for the Netduino cube.
    /// </summary>
    public class Program
    {
        #region Enums

        /// <summary>
        /// Which axis should we rotate around?
        /// </summary>
        private enum AxisOfRotation
        {
            /// <summary>
            /// Rotate around the X axis.
            /// </summary>
            X,

            /// <summary>
            /// Rotate arouns the Y axis.
            /// </summary>
            Y,

            /// <summary>
            /// Rotate around the Z axiz.
            /// </summary>
            Z
        };

        #endregion

        #region Private static variables.

        /// <summary>
        /// Letters holding the character set.
        /// </summary>
        private static PlanarBitmap[] characterSet = new PlanarBitmap[29];

        /// <summary>
        /// Set up and seed the random number generator.
        /// </summary>
        private static Random rand = new Random((int) (Utility.GetMachineTime().Ticks & 0xffffffff));

        /// <summary>
        /// Create a new instance of the cube.
        /// </summary>
        private static LEDCube cube = new LEDCube();

        /// <summary>
        /// Buffer to be used to hold the working (not displayed) copy of the cube data.
        /// </summary>
        private static byte[] newFrame = new byte[64];

        #endregion

        #region Generic support methods.

        /// <summary>
        /// Set or rest a specific pixel in the cube.
        /// </summary>
        /// <param name="x">X co-ordinate of the pixel to be set.</param>
        /// <param name="y">Y co-ordinate of thge pixel to be set.</param>
        /// <param name="z">Plane of the pixel to be set.</param>
        /// <param name="value">Turn the pixel on or off: true = turn on, false - turn off</param>
        private static void SetPixel(int x, int y, int z, bool value)
        {
            if ((x >= 0) && (x <= 7) && (y >= 0) && (y <= 7) && (z >= 0) && (z <= 7))
            {
                SetPixel(newFrame, x, y, z, value);
            }
        }

        /// <summary>
        /// Set or rest a specific pixel in the specified buffer.
        /// </summary>
        /// <param name="buffer">Buffer to set the pixel in.</param>
        /// <param name="x">X co-ordinate of the pixel to be set.</param>
        /// <param name="y">Y co-ordinate of thge pixel to be set.</param>
        /// <param name="z">Plane of the pixel to be set.</param>
        /// <param name="value">Turn the pixel on or off: true = turn on, false - turn off</param>
        private static void SetPixel(byte[] buffer, int x, int y, int z, bool value)
        {
            int whichOne = (y * 8) + z;
            if (value)
            {
                buffer[whichOne] |= (byte) ((1 << x) & 0xff);
            }
            else
            {
                buffer[whichOne] &= (byte) ((~(1 << x)) & 0xff);
            }
        }

        /// <summary>
        /// Get the value of a pixel
        /// </summary>
        /// <param name="x">X co-ordinate of the pixel to be set.</param>
        /// <param name="y">Y co-ordinate of thge pixel to be set.</param>
        /// <param name="z">PLane of the pixel to be set.</param>
        /// <returns>True if the pixel is turned on, false otherwise.</returns>
        private static bool GetPixel(byte[] buffer, int x, int y, int z)
        {
            int whichOne = (y * 8) + z;
            return ((buffer[whichOne] & ((byte) ((1 << x) & 0xff))) != 0);
        }

        /// <summary>
        /// Clear the cube.
        /// </summary>
        /// <remarks>
        /// Clear the cube of data and refresh the cube buffer.
        /// </remarks>
        private static void ClearCube()
        {
            ClearBuffer(newFrame);
            cube.ClearCube();
        }

        /// <summary>
        /// Clear the specified buffer (i.e. set each byte to zero).
        /// </summary>
        /// <param name="buffer">Buffer to clear.</param>
        private static void ClearBuffer(byte[] buffer)
        {
            for (int count = 0; count < buffer.Length; count++)
            {
                buffer[count] = 0;
            }
        }

        /// <summary>
        /// Coipy one frame (buffer) into a new frame (buffer);
        /// </summary>
        /// <param name="source">Source bytes to copy.</param>
        /// <param name="destination">Destination location for the copy of the data.</param>
        private static void CopyFrame(byte[] source, byte[] destination)
        {
            for (int count = 0; count < source.Length; count++)
            {
                destination[count] = source[count];
            }
        }

        #endregion

        #region Some test methods.

        /// <summary>
        /// A quick test to verify that all of the LEDs work correctly.
        /// </summary>
        private static void Test()
        {
            ClearCube();
            while (true)
            {
                for (int bit = 0; bit < 8; bit++)
                {
                    byte v;
                    v = (byte) ((1 << bit) & 0xff);
                    for (int count = 0; count < 64; count++)
                    {
                        newFrame[count] = v;
                    }
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(1);
                }
            }
        }

        /// <summary>
        /// Test all of the LEDs in the cube.
        /// </summary>
        private static void TestAllLeds()
        {
            for (int z = 0; z < 8; z++)
            {
                for (int y = 0; y < 8; y++)
                {
                    for (int x = 0; x < 8; x++)
                    {
                        ClearCube();
                        SetPixel(x, y, z, true);
                        cube.UpdateBuffer(newFrame);
                        Thread.Sleep(5);
                    }
                }
            }
        }

        /// <summary>
        /// Cycle through each LED in the specified layer.
        /// </summary>
        /// <param name="layer">Layer to test.</param>
        private static void TestLayer(int layer)
        {
            for (int z = 0; z < 8; z++)
            {
                for (int x = 0; x < 8; x++)
                {
                    ClearCube();
                    SetPixel(x, layer, z, true);
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(5);
                }
            }
        }

        #endregion

        #region Bouncing planes.

        /// <summary>
        /// Show planes moving from the bottom of the cube to the top of the cube.
        /// </summary>
        private static void BouncePlaneBottomToTop(int noCycles)
        {
            int direction = 1;
            int plane = 0;
            for (int repeat = 0; repeat < noCycles; repeat++)
            {
                for (int index = 0; index < 8; index++)
                {
                    ClearCube();
                    int offset = plane * 8;
                    for (int counter = 0; counter < 8; counter++)
                    {
                        newFrame[offset + counter] = 0xff;
                    }
                    plane += direction;
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
                direction *= -1;
                if (plane == 8)
                {
                    plane = 7;
                }
                if (plane < 0)
                {
                    plane = 0;
                }
            }
        }

        /// <summary>
        /// Move a plane from the back of the cube to the front of the cube.
        /// </summary>
        private static void BouncePlaneLeftToRight(int noCycles)
        {
            for (int repeat = 0; repeat < noCycles; repeat++)
            {
                byte value = 1;

                for (int index = 0; index < 8; index++)
                {
                    for (int counter = 0; counter < newFrame.Length; counter++)
                    {
                        newFrame[counter] = value;
                    }
                    cube.UpdateBuffer(newFrame);
                    value <<= 1;
                    Thread.Sleep(100);
                }
                value = 128;
                for (int index = 7; index >= 0; index--)
                {
                    for (int counter = 0; counter < newFrame.Length; counter++)
                    {
                        newFrame[counter] = value;
                    }
                    cube.UpdateBuffer(newFrame);
                    value >>= 1;
                    Thread.Sleep(100);
                }
            }
        }

        /// <summary>
        /// Show a plane moving from the back to the front of the cube.
        /// </summary>
        private static void BouncePlaneBackToFront(int noCycles)
        {
            int direction = 1;
            int plane = 0;

            for (int repeat = 0; repeat < noCycles; repeat++)
            {
                byte value = 1;
                for (int index = 0; index < 8; index++)
                {
                    ClearCube();
                    for (int counter = 0; counter < 8; counter++)
                    {
                        newFrame[(counter * 8) + plane] = 0xff;
                    }
                    cube.UpdateBuffer(newFrame);
                    plane += direction;
                    value <<= 1;
                    Thread.Sleep(100);
                }
                direction *= -1;
                if (plane == 8)
                {
                    plane = 7;
                }
                if (plane < 0)
                {
                    plane = 0;
                }
            }
        }

        #endregion

        #region Showing a message.

        /// <summary>
        /// Get the dot pattern with the specified name.
        /// </summary>
        /// <param name="name">Name of the bitmap to look up.</param>
        /// <returns>Array of bytes containing the dot patern for the character.</returns>
        private static byte[] LookupPattern(string name)
        {
            foreach (PlanarBitmap l in characterSet)
            {
                if (l.Name == name)
                {
                    return (l.DotPattern);
                }
            }
            return (null);
        }

        /// <summary>
        /// Show the string from the back to the front of the cube.
        /// </summary>
        /// <param name="message">Message to display.</param>
        private static void ShowMessage(string message)
        {
            for (int index = 0; index < message.Length; index++)
            {
                byte[] dotPattern = LookupPattern(message[index].ToString());
                for (int plane = 0; plane < 8; plane++)
                {
                    ClearCube();
                    if (dotPattern != null)
                    {
                        for (int row = 0; row < 8; row++)
                        {
                            newFrame[(row * 8) + plane] = dotPattern[row];
                        }
                        cube.UpdateBuffer(newFrame);
                    }
                    Thread.Sleep(50);
                }
                ClearCube();
            }
        }

        #endregion

        #region Rain effect methods.

        /// <summary>
        /// Add rain drops to the cube.
        /// </summary>
        /// <remarks>
        /// The algorithm will always add the specified number of rain drops to the cube.
        /// If the selected position is already occupied then a new psoition will be
        /// selected.
        /// </remarks>
        /// <param name="count">Number of rain drops to add.</param>
        /// <param name="plane">
        /// Starting plane.  -1 indicates the plane should be random otherwise the 
        /// specified starting plane will be used.
        /// </param>
        private static void AddDrops(int count, int plane = -1)
        {
            for (int drops = 0; drops < count; drops++)
            {
                bool findingSpace = true;
                while (findingSpace)
                {
                    int x = rand.Next() % 8;
                    int y = rand.Next() % 8;
                    int z;

                    if (plane == -1)
                    {
                        z = rand.Next() % 8;
                    }
                    else
                    {
                        z = plane;
                    }

                    int position = (z * 8) + y;
                    byte value = (byte) ((1 << x) & 0xff);
                    if ((newFrame[position] & value) == 0)            // Check if there is already a rain drop in this position.
                    {
                        newFrame[position] |= value;
                        findingSpace = false;
                    }
                }
            }
        }

        /// <summary>
        /// Produce an effect of rain falling through the cube.
        /// </summary>
        /// <remarks>
        /// The initial position of the drops is random  New drops will always be
        /// added in to the top plane.
        /// 
        /// The number of drops leaving the cube will be calculated and the same 
        /// number of drops will always be added in at the top of the cube.
        /// </remarks>
        /// <param name="noDrops">Number of drops to start with.</param>
        /// <param name="cycles">
        /// Number of cycles to run for.  One cycle represnes the movement of one 
        /// drop down through the cube by one layer.
        /// </param>
        private static void Rain(int noDrops, int cycles)
        {
            ClearCube();
            AddDrops(noDrops);
            cube.UpdateBuffer(newFrame);
            for (int currentCycle = 0; currentCycle < cycles; currentCycle++)
            {
                #region Work out how many drops are on the bottom plane.

                int bitCount = 0;
                for (int verticalPlane = 0; verticalPlane < 8; verticalPlane++)
                {
                    byte value = 1;
                    for (int bit = 0; bit < 8; bit++)
                    {
                        if ((newFrame[verticalPlane] & value) > 0)
                        {
                            bitCount++;
                        }
                        value <<= 1;
                    }
                }

                #endregion

                #region Move the drops down by one plane and clear the top plane.

                for (int plane = 8; plane < 64; plane++)
                {
                    newFrame[plane - 8] = newFrame[plane];
                }
                for (int b = 56; b < 64; b++)
                {
                    newFrame[b] = 0;
                }

                #endregion

                AddDrops(bitCount, 7);      // Add in the number of drops leaving to the top plane.
                cube.UpdateBuffer(newFrame);
                Thread.Sleep(75);
            }
        }

        #endregion

        #region Wave effects.

        /// <summary>
        /// Perform a Mexican wave.
        /// </summary>
        /// <param name="noCycles">Number of complete waves to perform.</param>
        private static void MexicanWave(int noCycles = 10)
        {
            ClearCube();
            for (int z = 0; z < 8; z++)
            {
                SetPixel(0, 7, z, true);
            }
            for (int cycle = 0; cycle < noCycles; cycle++)
            {
                for (int height = 7; height >= 0; height--)
                {
                    for (int bytes = 0; bytes < 64; bytes++)
                    {
                        newFrame[bytes] <<= 1;
                    }
                    for (int z = 0; z < 8; z++)
                    {
                        SetPixel(0, height, z, true);
                    }
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
                for (int height = 7; height >= 0; height--)
                {
                    for (int bytes = 0; bytes < 64; bytes++)
                    {
                        newFrame[bytes] <<= 1;
                    }
                    for (int z = 0; z < 8; z++)
                    {
                        SetPixel(0, 7 - height, z, true);
                    }
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
            }
        }

        /// <summary>
        /// Show a triangular wave.
        /// </summary>
        /// <param name="noCycles">Number of complete waves to perform.</param>
        private static void TriangularWave(int noCycles = 10)
        {
            ClearCube();
            for (int cycle = 0; cycle < noCycles; cycle++)
            {
                for (int height = 7; height >= 0; height--)
                {
                    for (int bytes = 0; bytes < 64; bytes++)
                    {
                        newFrame[bytes] <<= 1;
                    }
                    for (int z = 0; z < 8; z++)
                    {
                        SetPixel(z, height, z, true);
                    }
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
                for (int height = 7; height >= 0; height--)
                {
                    for (int bytes = 0; bytes < 64; bytes++)
                    {
                        newFrame[bytes] <<= 1;
                    }
                    for (int z = 0; z < 8; z++)
                    {
                        SetPixel(z, 7 - height, z, true);
                    }
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
            }
        }

        #endregion

        #region Rotation Effects.

        /// <summary>
        /// Rotate the source buffer around a particular point in a plane.
        /// </summary>
        /// <param name="axis">Axis to rotate arouns.</param>
        /// <param name="xOffset">X offset of the origin.</param>
        /// <param name="yOffset">Y offset of the origin.</param>
        /// <param name="zOffset">Z offset of the origin.</param>
        /// <param name="theta">angle to roate through.</param>
        /// <param name="source">Buffer holding the original data.</param>
        /// <param name="destination">New buffer holding the rotated pixels.</param>
        private static void RotateAroundAxis(AxisOfRotation axis, float xOffset, float yOffset, float zOffset, int theta, byte[] source, byte[] destination)
        {
            ClearBuffer(destination);
            float cosTheta = ((float) Microsoft.SPOT.Math.Cos(theta)) / 1000;
            float sinTheta = ((float) Microsoft.SPOT.Math.Sin(theta)) / 1000;
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    for (int z = 0; z < 8; z++)
                    {
                        if (GetPixel(source, x, y, z))
                        {
                            float xCoord, yCoord, zCoord;
                            int newX = 0, newY = 0, newZ = 0;

                            switch (axis)
                            {
                                case AxisOfRotation.X:
                                    xCoord = x - xOffset;
                                    yCoord = y - yOffset;
                                    newX = (int) System.Math.Round(((xCoord * cosTheta) - (yCoord * sinTheta)) + xOffset);
                                    newY = (int) System.Math.Round(((xCoord * sinTheta) + (yCoord * cosTheta)) + yOffset);
                                    newZ = z;
                                    break;
                                case AxisOfRotation.Y:
                                    xCoord = x - xOffset;
                                    zCoord = z - zOffset;
                                    newX = (int) System.Math.Round(((zCoord * sinTheta) + (xCoord * cosTheta)) + xOffset);
                                    newY = y;
                                    newZ = (int) System.Math.Round(((zCoord * cosTheta) - (xCoord * sinTheta)) + zOffset);
                                    break;
                                case AxisOfRotation.Z:
                                    yCoord = y - yOffset;
                                    zCoord = z - zOffset;
                                    newX = x;
                                    newY = (int) System.Math.Round(((yCoord * cosTheta) - (zCoord * sinTheta)) + xOffset);
                                    newZ = (int) System.Math.Round(((yCoord * sinTheta) + (zCoord * sinTheta)) + zOffset);
                                    break;
                            }

                            if ((newX >= 0) && (newX <= 7) && (newY >= 0) && (newY <= 7) && (newZ >= 0) && (newZ <= 7))
                            {
                                SetPixel(destination, newX, newY, newZ, true);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Spin the given buffer through 360 degrees for the specified number of times.
        /// </summary>
        /// <param name="buffer">Cube buffer to rotate.</param>
        /// <param name="x">x Coordinat to spin the buffer around (default = 3.5)</param>
        /// <param name="y">y Coordinat to spin the buffer around (default = 3.5)</param>
        /// <param name="z">z Coordinat to spin the buffer around (default = 3.5)</param>
        /// <returns>16 frame buffer which has been animated.</returns>
        private static byte[][] SpinBufferAroundYAxis(byte[] buffer, float x = 3.5F, float y = 3.5F, float z = 3.5F)
        {
            byte[][] frames = new byte[16][];
            frames[0] = new byte[64];
            CopyFrame(buffer, frames[0]);
            for (int iteration = 1; iteration < 16; iteration++)
            {
                frames[iteration] = new byte[64];
                RotateAroundAxis(AxisOfRotation.Y, x, y, z, (int) (22.5 * iteration), frames[0], frames[iteration]);
            }
            return (frames);
        }

        /// <summary>
        /// Put the requested letter in the centre of the cube and make it spin.
        /// </summary>
        /// <remarks>
        /// This method spins a character round the centre of the cube.  It works but does require
        /// the viewer to squint a little to best appreciate the effect.
        /// </remarks>
        /// <param name="character">Letter to show in the cube.</param>
        /// <param name="cycles">Number of rotations to make.</param>
        /// <param name="pause">How long (in milliseconds) between the movement from one angle to the next.</param>
        /// <returns>Animation ready for playing.</returns>
        private static void PrepareSpinningLetter(ArrayList animations, string character, int cycles, int pause = 20)
        {
            Debug.Print("Preparing spinning letter - " + character.ToString());
            ClearCube();
            byte[] dotPattern = LookupPattern(character);
            if (dotPattern != null)
            {
                for (int row = 0; row < 8; row++)
                {
                    newFrame[(row * 8) + 3] = dotPattern[row];
                }
            }
            Animation spinningLetter = new Animation();
            spinningLetter.Frames = SpinBufferAroundYAxis(newFrame, 3, 3, 3);
            spinningLetter.Repeat = cycles;
            spinningLetter.FrameDelay = pause;
            animations.Add(spinningLetter);
            Debug.Print("Spinning letter complete.");
        }

        /// <summary>
        /// Display a spinning horizontal bar.
        /// </summary>
        /// <param name="animations">Animation collection to add this animation to.</param>
        /// <param name="cycles">Number of cycles to display the bar.</param>
        /// <param name="pause">Pause between angular rotations.</param>
        private static void PrepareSpinningBar(ArrayList animations, int cycles, int pause)
        {
            Debug.Print("Preparing spinning bar.");
            ClearCube();
            for (int x = 0; x < 8; x++)
            {
                SetPixel(x, 3, 3, true);
            }
            Animation spinningBar = new Animation();
            spinningBar.Frames = SpinBufferAroundYAxis(newFrame);
            spinningBar.Repeat = cycles;
            spinningBar.FrameDelay = pause;
            animations.Add(spinningBar);
            Debug.Print("Spinning bar complete.");
        }

        /// <summary>
        /// Spin a vertical plane.
        /// </summary>
        /// <param name="animations">Animation collection to add this animation to.</param>
        /// <param name="cycles">Number of time to repeat the animation.</param>
        /// <param name="pause">Pause between the angular rotations.</param>
        private static void PrepareSpinningPlanes(ArrayList animations, int cycles, int pause)
        {
            Animation leadInAnimation = new Animation();
            Animation leadOutAnimation = new Animation();
            Animation spinningAnimation = new Animation();

            Debug.Print("Preparing spinning plane.");
            leadInAnimation.Frames = new byte[8][];
            //
            //  Now slide in the layer.
            //
            ClearCube();
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, true);
                }
                leadInAnimation.Frames[x] = new byte[64];
                CopyFrame(newFrame, leadInAnimation.Frames[x]);
            }
            animations.Add(leadInAnimation);
            //
            //  Setup the spinning animation.
            //
            ClearCube();
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, true);
                }
            }
            spinningAnimation.Frames = SpinBufferAroundYAxis(newFrame);
            spinningAnimation.Repeat = cycles;
            spinningAnimation.FrameDelay = pause;
            animations.Add(spinningAnimation);
            //
            //  Slide out the layer.
            //
            leadOutAnimation.Frames = new byte[8][];
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, false);
                }
                leadOutAnimation.Frames[x] = new byte[64];
                CopyFrame(newFrame, leadOutAnimation.Frames[x]);
            }
            animations.Add(leadOutAnimation);
            Debug.Print("Spinning plane complete.");
        }

        /// <summary>
        /// Place a vertical plane in the cube and then split it in two horizontally.  Spin the
        /// top half of the planani-clockwise around Y and spin the bottom half of the plane
        /// clockwise around the Y axis.
        /// </summary>
        /// <param name="animations">Animation collection to add this animation to.</param>
        /// <param name="cycles">Number of time to repeat the animation.</param>
        /// <param name="pause">Pause between the angular rotations.</param>
        private static void PrepareContraRotatingPlanes(ArrayList animations, int cycles, int pause)
        {
            Animation leadInAnimation = new Animation();
            Animation leadOutAnimation = new Animation();
            Animation spinningAnimation = new Animation();

            Debug.Print("Preparing contra rotating planes.");
            leadInAnimation.Frames = new byte[8][];
            //
            //  Slide in the layer.
            //
            ClearCube();
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, true);
                }
                leadInAnimation.Frames[x] = new byte[64];
                CopyFrame(newFrame, leadInAnimation.Frames[x]);
            }
            animations.Add(leadInAnimation);
            //
            //  Setup the spinning animation.
            //
            ClearCube();
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, true);
                }
            }
            spinningAnimation.Frames = SpinBufferAroundYAxis(newFrame);
            for (int frame = 15; frame >= 0; frame--)
            {
                for (int y = 0; y < 4; y++)
                {
                    for (int x = 0; x < 8; x++)
                    {
                        for (int z = 0; z < 8; z++)
                        {
                            SetPixel(spinningAnimation.Frames[15 - frame], x, y, z, GetPixel(spinningAnimation.Frames[frame], x, 4, z));
                        }
                    }
                }
            }
            spinningAnimation.Repeat = cycles;
            spinningAnimation.FrameDelay = pause;
            animations.Add(spinningAnimation);
            //
            //  Slide out the layer.
            //
            leadOutAnimation.Frames = new byte[8][];
            for (int x = 0; x < 8; x++)
            {
                for (int y = 0; y < 8; y++)
                {
                    SetPixel(x, y, 7 - x, false);
                }
                leadOutAnimation.Frames[x] = new byte[64];
                CopyFrame(newFrame, leadOutAnimation.Frames[x]);
            }
            animations.Add(leadOutAnimation);
            Debug.Print("Spinning plane complete.");
        }

        /// <summary>
        /// Prepare a spinning cross made connecting the diagonals.
        /// </summary>
        /// <param name="animations">Animation collection to add this animation to.</param>
        /// <param name="cycles">Number of times to rotate the cross.</param>
        /// <param name="pause">Pause between the angular rotations.</param>
        private static void PrepareSpinningDiagonals(ArrayList animations, int cycles, int pause)
        {
            ClearBuffer(newFrame);
            for (int counter = 0; counter < 8; counter++)
            {
                SetPixel(counter, counter, counter, true);
                SetPixel(counter, 7 - counter, 7 - counter, true);
                SetPixel(7 - counter, 7 - counter, counter, true);
                SetPixel(7 - counter, counter, 7 - counter, true);
            }
            Animation spinningDiagonals = new Animation();
            spinningDiagonals.Frames = SpinBufferAroundYAxis(newFrame, 3.5F, 3.5F, 3.5F);
            spinningDiagonals.Repeat = cycles;
            spinningDiagonals.FrameDelay = pause;
            animations.Add(spinningDiagonals);
        }

        #endregion

        #region Cube methods.

        /// <summary>
        /// Slide a 4 x 4 cube around the bottom layer of the cube.
        /// </summary>
        private static void SlidingCube()
        {
            ClearCube();
            for (int x = 0; x < 4; x++)
            {
                for (int y = 0; y < 4; y++)
                {
                    for (int z = 0; z < 4; z++)
                    {
                        SetPixel(x, y, z, true);
                    }
                }
            }
            cube.UpdateBuffer(newFrame);
            Thread.Sleep(100);
            for (int x = 4; x <= 7; x++)
            {
                for (int y = 0; y < 4; y++)
                {
                    for (int z = 0; z < 4; z++)
                    {
                        SetPixel(x, y, z, true);
                        SetPixel(x - 4, y, z, false);
                    }
                }
                cube.UpdateBuffer(newFrame);
                Thread.Sleep(100);
            }
            for (int z = 4; z <= 7; z++)
            {
                for (int x = 4; x <= 7; x++)
                {
                    for (int y = 0; y < 4; y++)
                    {
                        SetPixel(x, y, z, true);
                        SetPixel(x, y, z - 4, false);
                    }
                }
                cube.UpdateBuffer(newFrame);
                Thread.Sleep(100);
            }
            for (int x = 7; x >= 4; x--)
            {
                for (int y = 0; y < 4; y++)
                {
                    for (int z = 4; z <= 7; z++)
                    {
                        SetPixel(x, y, z, false);
                        SetPixel(x - 4, y, z, true);
                    }
                }
                cube.UpdateBuffer(newFrame);
                Thread.Sleep(100);
            }
            for (int z = 7; z >= 4; z--)
            {
                for (int x = 0; x <= 4; x++)
                {
                    for (int y = 0; y <= 4; y++)
                    {
                        SetPixel(x, y, z - 4, true);
                        SetPixel(x, y, z, false);
                    }
                }
                cube.UpdateBuffer(newFrame);
                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// Create a pulsating cube animation.
        /// </summary>
        /// <param name="animations">Animations collaction to add this animation to.</param>
        /// <param name="cycles">Number of times to repeat the animation.</param>
        /// <param name="pause">Number of milliseconds to pause between the frames.</param>
        private static void PreparePulsingCube(ArrayList animations, int cycles, int pause)
        {
            Animation pulsingCube = new Animation();
            pulsingCube.Frames = new byte[6][];
            for (int lower = 3; lower > 0; lower--)
            {
                pulsingCube.Frames[3 - lower] = new byte[64];
                for (int x = lower; x <= 7 - lower; x++)
                {
                    for (int y = lower; y <= 7 - lower; y++)
                    {
                        for (int z = lower; z <= 7 - lower; z++)
                        {
                            SetPixel(pulsingCube.Frames[3 - lower], x, y, z, true);
                        }
                    }
                }
            }
            for (int counter = 0; counter < 3; counter++)
            {
                pulsingCube.Frames[5 - counter] = new byte[64];
                CopyFrame(pulsingCube.Frames[counter], pulsingCube.Frames[5 - counter]);
            }
            pulsingCube.Repeat = cycles;
            pulsingCube.FrameDelay = pause;
            animations.Add(pulsingCube);
        }

        #endregion

        #region Fireworks.

        /// <summary>
        /// Add the fireworks to the cube frame buffer.
        /// </summary>
        /// <param name="fireworks">Array of fireworks to add.</param>
        private static void AddFireworksToBuffer(Firework[] fireworks)
        {
            ClearCube();
            foreach (Firework firework in fireworks)
            {
                if (firework.IsClimbing)
                {
                    SetPixel(firework.X, firework.CurrentHeight, firework.Z, true);
                }
                if (firework.IsExploding)
                {
                    foreach (Point shard in firework.Shards)
                    {
                        SetPixel(shard.X, shard.Y, shard.Z, true);
                    }
                }
            }
        }

        /// <summary>
        /// Create a firework display in the cube.
        /// </summary>
        /// <param name="numberOfFireworks">Number of fireworks to add to the cube.</param>
        /// <param name="noCycles">Number of cycles to run this animation for.</param>
        private static void FireworkDisplay(int numberOfFireworks, int noCycles = 10)
        {
            Firework[] fireworks = new Firework[numberOfFireworks];
            for (int counter = 0; counter < numberOfFireworks; counter++)
            {
                fireworks[counter] = new Firework();
            }
            bool processing = true;
            while (processing)
            {
                processing = false;
                for (int counter = 0; counter < numberOfFireworks; counter++)
                {
                    processing |= fireworks[counter].Move();
                }
                if (processing)
                {
                    AddFireworksToBuffer(fireworks);
                    cube.UpdateBuffer(newFrame);
                    Thread.Sleep(100);
                }
            }
        }

        #endregion

        /// <summary>
        /// Main program loop.
        /// </summary>
        public static void Main()
        {
            #region Setup the character set.

            characterSet[0] = new PlanarBitmap("A", new byte[] { 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x1C, 0x0 });
            characterSet[1] = new PlanarBitmap("B", new byte[] { 0x1E, 0x22, 0x22, 0x1E, 0x22, 0x22, 0x1E, 0x0 });
            characterSet[2] = new PlanarBitmap("C", new byte[] { 0x1C, 0x22, 0x2, 0x2, 0x2, 0x22, 0x1C, 0x0 });
            characterSet[3] = new PlanarBitmap("D", new byte[] { 0xE, 0x12, 0x22, 0x22, 0x22, 0x12, 0xE, 0x0 });
            characterSet[4] = new PlanarBitmap("E", new byte[] { 0x3E, 0x2, 0x2, 0x1E, 0x2, 0x2, 0x3E, 0x0 });
            characterSet[5] = new PlanarBitmap("F", new byte[] { 0x2, 0x2, 0x2, 0x1E, 0x2, 0x2, 0x3E, 0x0 });
            characterSet[6] = new PlanarBitmap("G", new byte[] { 0x1C, 0x22, 0x22, 0x3A, 0x2, 0x22, 0x1C, 0x0 });
            characterSet[7] = new PlanarBitmap("H", new byte[] { 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x0 });
            characterSet[8] = new PlanarBitmap("I", new byte[] { 0x3E, 0x8, 0x8, 0x8, 0x8, 0x8, 0x3E, 0x0 });
            characterSet[9] = new PlanarBitmap("J", new byte[] { 0xC, 0x12, 0x10, 0x10, 0x10, 0x10, 0x38, 0x0 });
            characterSet[10] = new PlanarBitmap("K", new byte[] { 0x22, 0x12, 0xA, 0x6, 0xA, 0x12, 0x22, 0x0 });
            characterSet[11] = new PlanarBitmap("L", new byte[] { 0x3E, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x0 });
            characterSet[12] = new PlanarBitmap("M", new byte[] { 0x22, 0x22, 0x22, 0x2A, 0x2A, 0x36, 0x22, 0x0 });
            characterSet[13] = new PlanarBitmap("N", new byte[] { 0x22, 0x22, 0x32, 0x2A, 0x26, 0x22, 0x22, 0x0 });
            characterSet[14] = new PlanarBitmap("O", new byte[] { 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x0 });
            characterSet[15] = new PlanarBitmap("P", new byte[] { 0x2, 0x2, 0x2, 0x1E, 0x22, 0x22, 0x1E, 0x0 });
            characterSet[16] = new PlanarBitmap("Q", new byte[] { 0x2C, 0x12, 0x2A, 0x22, 0x22, 0x22, 0x1C, 0x0 });
            characterSet[17] = new PlanarBitmap("R", new byte[] { 0x22, 0x12, 0xA, 0x1E, 0x22, 0x22, 0x1E, 0x0 });
            characterSet[18] = new PlanarBitmap("S", new byte[] { 0x1E, 0x20, 0x20, 0x1C, 0x2, 0x2, 0x3C, 0x0 });
            characterSet[19] = new PlanarBitmap("T", new byte[] { 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x3E, 0x0 });
            characterSet[20] = new PlanarBitmap("U", new byte[] { 0x1C, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0 });
            characterSet[21] = new PlanarBitmap("V", new byte[] { 0x8, 0x14, 0x22, 0x22, 0x22, 0x22, 0x22, 0x0 });
            characterSet[22] = new PlanarBitmap("W", new byte[] { 0x14, 0x2A, 0x2A, 0x2A, 0x22, 0x22, 0x22, 0x0 });
            characterSet[23] = new PlanarBitmap("X", new byte[] { 0x22, 0x22, 0x14, 0x8, 0x14, 0x22, 0x22, 0x0 });
            characterSet[24] = new PlanarBitmap("Y", new byte[] { 0x8, 0x8, 0x8, 0x8, 0x36, 0x22, 0x22, 0x0 });
            characterSet[25] = new PlanarBitmap("Z", new byte[] { 0x3E, 0x2, 0x4, 0x8, 0x10, 0x20, 0x3E, 0x0 });
            characterSet[26] = new PlanarBitmap("RightArrow", new byte[] { 0x10, 0x30, 0x7F, 0xFF, 0x70, 0x30, 0x10, 0x0 });
            characterSet[27] = new PlanarBitmap("Circle", new byte[] { 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18 });
            characterSet[28] = new PlanarBitmap("SnowFlake", new byte[] { 0x99, 0x42, 0x24, 0x99, 0x99, 0x24, 0x42, 0x99 });

            #endregion

            ClearCube();
            Thread display = new Thread(new ThreadStart(cube.DisplayBuffer));
            display.Start();
            ArrayList animations = new ArrayList();
            PreparePulsingCube(animations, 2, 200);
            PrepareSpinningLetter(animations, "S", 2, 100);
            PrepareSpinningLetter(animations, "P", 2, 100);
            PrepareSpinningLetter(animations, "I", 2, 100);
            PrepareSpinningLetter(animations, "N", 2, 100);
            PrepareSpinningPlanes(animations, 2, 100);
            PrepareContraRotatingPlanes(animations, 2, 100);
            PrepareSpinningLetter(animations, "Circle", 4, 100);
            PrepareSpinningLetter(animations, "SnowFlake", 4, 100);
            while (true)
            {
                ShowMessage("NETDUINOROCKS");
                TriangularWave(4);
                MexicanWave(4);
                foreach (Animation animation in animations)
                {
                    animation.Play(cube);
                }
                Rain(10, 100);
                for (int counter = 0; counter < 10; counter++)
                {
                    FireworkDisplay(1);
                }
            }
        }
    }
}